Обзор датасета¶

In [552]:
import pandas as pd
import numpy as np
import cv2
from matplotlib import pyplot as plt
import matplotlib.patches as patches

from sklearn.model_selection import train_test_split
import shutil
import os
import sklearn
from sklearn.metrics import confusion_matrix
import itertools 
import random
In [5]:
PATH = 'D:/Colab Notebooks/'
PATH_IMG = PATH + 'connected_images/'

Посмотрим на изображения и csv файл

In [7]:
test = pd.read_csv(PATH + 'test.csv')
test_encoded = pd.read_csv(PATH + 'test_encoded.csv')
train = pd.read_csv(PATH + 'train.csv')
train_encoded = pd.read_csv(PATH + 'train_encoded.csv')

data = {'test': test, 'test_encoded': test_encoded, 'train': train, 'train_encoded': train_encoded}
In [8]:
print(f"Объем обучающей выборки = {len(train)} \nОбъем тестовой выборки = {len(test)}")
Объем обучающей выборки = 2541 
Объем тестовой выборки = 1688
In [9]:
train.head()
Out[9]:
Unnamed: 0 image_name type xmin xmax ymin ymax
0 0 0007Date_01_08_2019.jpg other 285 368 61 278
1 1 0013Date_01_08_2019.jpg armature 187 550 101 253
2 2 0016Date_01_08_2019.jpg armature 172 327 13 360
3 3 0019Date_01_08_2019.jpg armature 19 267 162 237
4 4 0019Date_01_08_2019.jpg armature 309 548 300 376
In [10]:
train_encoded.head()
Out[10]:
Image_name_Type EncodedPixels
0 0007Date_01_08_2019.jpg_armature 1 1
1 0007Date_01_08_2019.jpg_other 43229 83 43933 83 44637 83 45341 83 46045 83 4...
2 0007Date_01_08_2019.jpg_wood 1 1
3 0013Date_01_08_2019.jpg_armature 71291 363 71995 363 72699 363 73403 363 74107 ...
4 0013Date_01_08_2019.jpg_other 1 1

Посмотрим, есть ли пропуски в данных

In [11]:
def check_empty(table):
    headers = table.columns
    count_null = pd.DataFrame(table[headers].isnull().sum())
    count_null = count_null.reset_index()
    count_null.rename(columns = {'index' : 'Column', 0 : 'Count_null'}, inplace = True) 
    
    return count_null
In [12]:
for key in data:
    print(key + ':')
    print(check_empty(data[key]), '\n\n')
test:
       Column  Count_null
0  Unnamed: 0           0
1  image_name           0
2        type           0
3        xmin           0
4        xmax           0
5        ymin           0
6        ymax           0
7       Usage           0 


test_encoded:
            Column  Count_null
0  Image_name_Type           0
1    EncodedPixels           0
2            Usage           0 


train:
       Column  Count_null
0  Unnamed: 0           0
1  image_name           0
2        type           0
3        xmin           0
4        xmax           0
5        ymin           0
6        ymax           0 


train_encoded:
            Column  Count_null
0  Image_name_Type           0
1    EncodedPixels           0 


Пропусков нет

Посмотрим на соотношение размеров классов

In [13]:
h = train['type'].hist()
fig = h.get_figure()

Как видно, выборка не сбалансирована

В идеале, нужно применить сэмплинг

Посмторим на экземпляры классов и выделенные области

In [14]:
pic_box = plt.figure(figsize=(50,50))

i = 0
for t in ['armature', 'wood', 'other']: 
    for _, row in train[train['type'] == t].sample(3).iterrows():
        img = cv2.imread(os.path.join(PATH_IMG, row['image_name']))
        img = cv2.rectangle(img, (row['xmin'], row['ymin']), (row['xmax'], row['ymax']), (0, 255, 0), 2)
        pic_box.add_subplot(3, 3, i+1)
        plt.text(0, 0, t, fontsize=35, color='black')
        plt.imshow(img)
        plt.axis('off')
        i+=1
    
plt.show()

Данный набор отлично подходит для задачи Object Detection (мне стало интересно и я ее сделала в конце).

Для решения поставленной задачи необходимо дополнительно разметить изображения для Instance Segmentation

Instance Segmentation¶

Разметка¶

  • Для разметки датасета я использовала MakeSense;
  • Формат аннотаций - VGG;

Размер выоборок

Я поделила изображения в следующем соотношении:

  • train = 550 (по ~180 на 1 класс)
  • val = 240 (по ~80 на класс)

В классе other всего 168 изображений, поэтому я использовала аугментацию

Результат сохранен в папку dataset, сама разметка хранится в файле via_redion_data.json

Обучение¶

Я использовала модель Mask R-CNN, с помощью которой можно получить необходимые маски

  1. Я скачала исходные файлы из репозитория на гите - https://github.com/matterport/Mask_RCNN
  2. Изменила некоторые файлы под свою задачу (измененный реп - https://github.com/alxvmr/mrcnn_yolo); *все шаги описаны в readme.txt
In [11]:
# метрики, полученные в ходе обучения
%reload_ext tensorboard
%tensorboard --logdir logs/custom3 --port 8088
Reusing TensorBoard on port 8088 (pid 4644), started 0:01:19 ago. (Use '!kill 4644' to kill it.)

На выходе получился довольно большой loss, но это оправдано размером обучающей выборки

Полученная модель¶

In [438]:
from mrcnn.mrcnn_colab_engine import *
import cv2
import warnings
warnings.filterwarnings('ignore')
In [549]:
import os
import sys
# Import Mask RCNN
import numpy as np
from skimage.measure import find_contours

from mrcnn.config import Config
import mrcnn.model as modellib
import colorsys
import random
import cv2


class InferenceConfig(Config):
    GPU_COUNT = 1
    IMAGES_PER_GPU = 1
    NUM_CLASSES = 4
    DETECTION_MIN_CONFIDENCE = 0.9
    NAME = 'ore'
In [532]:
# загрузим веса
MODEL_DIR = "logs/custom3/mask_rcnn_experiment_0040.h5"

config = InferenceConfig()
model = modellib.MaskRCNN(mode="inference", model_dir="logs/custom3", config=config)
model.load_weights(MODEL_DIR, by_name=True)
In [516]:
# добавим классы
class_names = ["BG", "armature", "wood", "other"]

# гнерация цветов
colors = None

Выделим объекты на изображении

In [550]:
img = cv2.imread("12812Date_06_08_2019.jpg")
plt.imshow(img)
Out[550]:
<matplotlib.image.AxesImage at 0x1d89a7189b0>
In [538]:
results = model.detect([img], verbose=1)
r = results[0]

new_img = visualize.display_instances(img, r['rois'], r['masks'], r['class_ids'], class_names, r['scores'])
plt.imshow(new_img)
Processing 1 images
image                    shape: (421, 704, 3)         min:    0.00000  max:  255.00000  uint8
molded_images            shape: (1, 512, 512, 3)      min: -123.70000  max:  149.10000  float64
image_metas              shape: (1, 16)               min:    0.00000  max:  704.00000  float64
anchors                  shape: (1, 65472, 4)         min:   -0.70849  max:    1.58325  float32
Out[538]:
<matplotlib.image.AxesImage at 0x1d8993617b8>

Маска, конечно, похожа на бред. Но обучающая выборка была слишком мала (и из метрик видно большую ошибку)

Процент загрязнения¶

Выведем чем и насколько загрязнена руда

In [450]:
# посчитаем площадь маски - колиество пикселей принадлежащих выделенной области
# => количесто элементов маски, равных True

def get_square(mask):
    count = 0
    for row in mask:
        for e in row:
            if (e):
                count += 1
    return count
In [519]:
# статистика загрязнения

def get_pollution_statistics(img, res):
    height, width, _ = img.shape
    square_img = height * width
    
    stat = {"armature": {"px": 0, "%": 0}, "wood": {"px": 0, "%": 0}, "other": {"px": 0, "%": 0}}
    
    for i, class_id in enumerate(res['class_ids']):
        cl = class_names[class_id]
        mask = res['masks'][:, :, i]
        if (cl in stat.keys()):
            stat[cl]["px"] += get_square(mask)
            
    for key in stat.keys():
        px = stat[key]["px"]
        if (px > 0.000005):
            stat[key]["%"] = px * 100 / square_img
    return stat
In [539]:
# посмотрим чем загрязнена руда и насколько
# px - общая площадь маски, % - процентное отношение к размеру изображения
get_pollution_statistics(img, r)
Out[539]:
{'armature': {'px': 0, '%': 0},
 'wood': {'px': 0, '%': 0},
 'other': {'px': 27403, '%': 9.245775750377888}}

Object detection¶

  • Была использована модель yolov3
  • Метрики подсчитывались автоматически в процессе обучения, с помощью wandb
In [540]:
dict_class = {'armature': "0", 'other': "1", 'wood': "2"}
PATH_LABELS = "C:/Users/User/yolov3-master/data/labels/"
PATH_IMG = "C:/Users/User/yolov3-master/data/images/"
PATH_DATA = "C:/Users/User/yolov3-master/data/"
data = {'test': test, 'train': train}
In [544]:
# выборка была разбита на обучающую и тестовую:
print(f"Объем обучающей выборки = {len(data['train'])};\nОбъем тестовой выборки = {len(data['test'])};")
Объем обучающей выборки = 2541;
Объем тестовой выборки = 1688;

Создадим директорию с текстовыми файлами - координаты рамок для каждого изображения

Например:

0068Date_01_08_2019

2 0.590199 0.876485 0.276989 0.218527

In [545]:
# for key in data:
#     for i, row in data[key].iterrows():
#         im = cv2.imread('D:/Colab Notebooks/connected_images/' + row['image_name'])
#         height, width, _ = im.shape
#         name = row["image_name"].split(".")[0]
#         img_class = dict_class[row["type"]]
#         x_c = (row["xmin"] + row["xmax"]) / 2.0
#         y_c = (row["ymin"] + row["ymax"]) / 2.0
#         widht_area = row["xmax"] - row["xmin"]
#         height_area = row["ymax"] - row["ymin"]
#         x_cn = round(x_c / width, 6)
#         y_cn = round(y_c / height, 6)
#         widht_area_n = round(widht_area / width, 6)
#         height_area_n = round(height_area / height, 6)
#         coords = str(x_cn) + " "\
#                  + str(y_cn) + " "\
#                  + str(widht_area_n) + " "\
#                  + str(height_area_n)
#         output_str = img_class + " " + coords
#         file = open(PATH_LABELS + name + ".txt", "w+")
#         file.write(output_str)
#         file.close()

Результат обучения¶

Обучение проводилось с параметрами epochs = 5, batch = 4.

Обучение заняло ~5 часов. Для столь небольшого времени обучения, результаты весьма неплохие.

In [546]:
img_labels = cv2.imread("C:/Users/User/yolov3-master/runs/train/exp33/val_batch0_labels.jpg")
img_predict = cv2.imread("C:/Users/User/yolov3-master/runs/train/exp33/val_batch0_pred.jpg")
conf_matrix = cv2.imread("C:/Users/User/yolov3-master/runs/train/exp33/confusion_matrix.png")
result = cv2.imread("C:/Users/User/yolov3-master/runs/train/exp33/results.png")
In [547]:
f, axarr = plt.subplots(1,2, figsize=(100, 100))
axarr[0].imshow(img_labels)
axarr[0].set_title('Исходное изображение (с обучающей разметкой)', fontsize=65)
axarr[1].imshow(img_predict)
axarr[1].set_title('Результат детектирования', fontsize=65)
Out[547]:
Text(0.5, 1.0, 'Результат детектирования')
In [548]:
# матрица ошибок
f, axarr = plt.subplots(1,2, figsize=(100, 100))
axarr[0].imshow(conf_matrix)
axarr[0].set_title('Матрица ошибок', fontsize=65)
axarr[1].imshow(result)
axarr[1].set_title('Результаты по метрикам', fontsize=65)
Out[548]:
Text(0.5, 1.0, 'Результаты по метрикам')

Как видно, у полученной модели проблемы с определением класса "other". Модель путает его с фоном.